#include <CL/cl.h>
#include <stdio.h>
#include <stdlib.h>
#include <wtypes.h>
#include <Strsafe.h>
#include <string.h>
#include <string>
#include <vector>
#include <omp.h>
#include <cstdlib>
#include <fstream>
#include <time.h>	
#include <math.h>
#include "MersenneTwister.h"
#include "WriteLog.h"

MersenneTwister::MersenneTwister(cl_uint count)
{
	width = count;
	initializeHost();
	initializeCL();
	runCLKernels();
}
MersenneTwister::~MersenneTwister()
{
	cleanupCL();
	cleanupHost();
}

void MersenneTwister::initializeHost(void)
{
	randomValue			= NULL;
	mtConfigs			= NULL;
	
	randomValue = (cl_float *) malloc(width * sizeof(cl_float));
	if(randomValue == NULL)
	{
		WriteLog::writeToLog("Failed to allocate randomValue memory on host", "Error: ", "MersenneTwister");
		return; 
	}

	srand((unsigned int) (time(NULL)));
	mtConfigs = (MersenneTwisterConfig*) malloc(width * sizeof(MersenneTwisterConfig));
	for(unsigned int i=0; i<width; i++)
	{
		double num = (rand() / (float) RAND_MAX) * 4194304;
		mtConfigs[i].mask_b = (unsigned int) num;
		mtConfigs[i].mask_c = (unsigned int) num;
		mtConfigs[i].matrix_a = (unsigned int) num;
		mtConfigs[i].seed = (unsigned int) num;
	}
	if(mtConfigs == NULL)
	{
		WriteLog::writeToLog("Failed to allocate 'MersenneTwisterConfig' memory on host", "Error: ", "MersenneTwister");
		return; 
	}
}

// OpenCL related initializations are done here.
// Context, Device list, Command Queue are set up.
// Calls are made to set up OpenCL memory buffers that this program uses
// and to load the programs into memory and get kernel handles.
void MersenneTwister::initializeCL(void)
{
	cl_int status = 0;
    size_t deviceListSize;

	// Create OpenCL memory buffers
	mtConfigsBuffer = clCreateBuffer(
						context, 
                       CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
                       sizeof(MersenneTwisterConfig) * width,
                       mtConfigs, 
                       &status);
	if(status != CL_SUCCESS) 
	{ 
		writeErrorToLog(status, "Can't create buffer 'mtConfigsBuffer' (clCreateBuffer)", "MersenneTwister");
		return;
	}
	// Create OpenCL memory buffers
    outputBuffer = clCreateBuffer(
					   context, 
                       CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
                       sizeof(cl_float) * width,
                       randomValue, 
                       &status);
    if(status != CL_SUCCESS) 
	{ 
		writeErrorToLog(status, "Can't create buffer 'outputBuffer' (clCreateBuffer)", "MersenneTwister");
		return;
	}
	
	// Load CL file, build CL program object, create CL kernel object
    const char * filename  = "MersenneTwister.cl";
    string  sourceStr = convertToString(filename);
    const char * source    = sourceStr.c_str();
    size_t sourceSize[]    = { strlen(source) };

    program = clCreateProgramWithSource(
			      context, 
                  1, 
                  &source,
				  sourceSize,
                  &status);
	if(status != CL_SUCCESS) 
	{ 
		writeErrorToLog(status, "Loading Binary into cl_program (clCreateProgramWithBinary)", "MersenneTwister");
		return;
	}

    // create a cl program executable for all the devices specified 
    status = clBuildProgram(program, 1, devices, NULL, NULL, NULL);
	if(status != CL_SUCCESS) 
	{ 
		writeErrorToLog(status, "Building Program (clBuildProgram)", "MersenneTwister");
		return; 
	}

    // get a kernel object handle for a kernel with the given name 
    kernel = clCreateKernel(program, "TestMersenneTwisters", &status);
    if(status != CL_SUCCESS) 
	{  
		writeErrorToLog(status, "Creating Kernel from program (clCreateKernel)", "MersenneTwister");
		return;
	}
}


// this is called once the OpenCL context, memory etc. are set up,
// the program is loaded into memory and the kernel handles are ready.
// It sets the values for kernels' arguments and enqueues calls to the kernels
// on to the command queue and waits till the calls have finished execution.
// It also gets kernel start and end time if profiling is enabled.
void MersenneTwister::runCLKernels(void)
{
	cl_int   status;
    cl_event events[2];
    size_t globalThreads[1];
    size_t localThreads[1];

    
    globalThreads[0] = width;
    localThreads[0]  = 1;

    // === Set appropriate arguments to the kernel ===
	status = clSetKernelArg(
					kernel, 
					0,				
					sizeof(cl_mem), 
					(void *)&mtConfigsBuffer);
	if(status != CL_SUCCESS) 
	{ 
		writeErrorToLog(status, "Setting kernel argument 'mtConfigsBuffer' (clSetKernelArg)", "MersenneTwister");
		return;
	}
    // the output array to the kernel 
    status = clSetKernelArg(
                    kernel, 
                    1,				// index of argument of kernel function
                    sizeof(cl_mem), 
                    (void *)&outputBuffer);
    if(status != CL_SUCCESS) 
	{ 
		writeErrorToLog(status, "Setting kernel argument 'outputBuffer' (clSetKernelArg)", "MersenneTwister");
		return;
	}

	status = clSetKernelArg(
					kernel, 
					2,				// index of argument of kernel function
					sizeof(cl_uint), 
					(void *)&width);
	if(status != CL_SUCCESS) 
	{ 
		printf("Error: .\n");
		writeErrorToLog(status, "Setting kernel argument 'width' (clSetKernelArg)", "MersenneTwister");
		return;
	}

    // Enqueue a kernel run call.
    status = clEnqueueNDRangeKernel(
			     commandQueue,
                 kernel,
                 1,
                 NULL,
                 globalThreads,
                 localThreads,
                 0,
                 NULL,
                 &events[0]);
    if(status != CL_SUCCESS) 
	{ 
		writeErrorToLog(status, "Enqueueing kernel onto command queue (clEnqueueNDRangeKernel)", "MersenneTwister");
		return;
	}

	clFinish(commandQueue);

    // wait for the kernel call to finish execution
    status = clWaitForEvents(1, &events[0]);
    if(status != CL_SUCCESS) 
	{ 
		writeErrorToLog(status, "Waiting for kernel run to finish (clWaitForEvents)", "MersenneTwister");
		return;
	}

    status = clReleaseEvent(events[0]);
    if(status != CL_SUCCESS)
    {
		writeErrorToLog(status, "Can't release event 'events[0]' (clReleaseEvent)", "MersenneTwister");
        return;
    }

    // Enqueue readBuffer
    status = clEnqueueReadBuffer(
                commandQueue,
                outputBuffer,
                CL_TRUE,
                0,
                width * sizeof(cl_float),
                randomValue,
                0,
                NULL,
                &events[1]);
    
    if(status != CL_SUCCESS) 
	{
		writeErrorToLog(status, "Can't read from buffer 'outputBuffer' (clEnqueueReadBuffer)", "MersenneTwister");
    }
	
    // Wait for the read buffer to finish execution
    status = clWaitForEvents(1, &events[1]);
    if(status != CL_SUCCESS) 
	{ 
		writeErrorToLog(status, "Waiting for read buffer call to finish (clWaitForEvents)", "MersenneTwister");
		return;
	}
    
    status = clReleaseEvent(events[1]);
    if(status != CL_SUCCESS)
    {
        writeErrorToLog(status, "Can't release event 'events[1]' (clReleaseEvent)", "MersenneTwister");
        return;
    }
}

void MersenneTwister::cleanupCL(void)
{
	cl_int status;

    status = clReleaseKernel(kernel);
    if(status != CL_SUCCESS)
	{
		writeErrorToLog(status, "Can't release kernel 'kernel' (clReleaseKernel)", "MersenneTwister");
		return; 
	}
    status = clReleaseProgram(program);
    if(status != CL_SUCCESS)
	{
		writeErrorToLog(status, "Can't release program 'program' (clReleaseProgram)", "MersenneTwister");
		return; 
	}
    
	status = clReleaseMemObject(outputBuffer);
    if(status != CL_SUCCESS)
	{
		writeErrorToLog(status, "Can't release memmory object 'outputBuffer' (clReleaseMemObject)", "MersenneTwister");
		return; 
	}

	status = clReleaseMemObject(mtConfigsBuffer);
    if(status != CL_SUCCESS)
	{
		writeErrorToLog(status, "Can't release memmory object 'mtConfigsBuffer' (clReleaseMemObject)", "MersenneTwister");
		return; 
	}

	MyOCL::cleanupCL();
}

void MersenneTwister::cleanupHost(void)
{
	if(randomValue != NULL)
	{
		free(randomValue);
		randomValue = NULL;
	}
	if(mtConfigs != NULL)
	{
		free(mtConfigs);
		mtConfigs = NULL;
	}
}